package com.djhu.springsimple.beans.factory;

import com.djhu.springsimple.beans.core.BeanDefinition;
import com.djhu.springsimple.beans.core.BeanFactoryAware;
import com.djhu.springsimple.beans.core.BeanNameAware;
import com.djhu.springsimple.beans.core.InitializingBean;

import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Logger;

/**
 * @author by zw on 2018/4/16.
 */
public abstract class BaseBeanFactory implements BeanFactory{

    private static Logger logger = Logger.getLogger(BeanFactory.class.getName());

    private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<String, BeanDefinition>();

    private List<BeanPostProcess>  beanPostProcesses = new ArrayList<BeanPostProcess>();

    private Set<String> beanDefinitionNames = new HashSet<String>();

    @Override
    public Object getBean(String name) throws Exception {
        if (!beanDefinitionMap.containsKey(name)){
             throw new RuntimeException("bean has not existed!!!" + name);
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if (beanDefinition.getBean() == null){
            Object bean = doCreatebean(beanDefinition);
            beanDefinition.setBean(bean);
            bean = initializeBean(beanDefinition, name);
            return bean;
        }
        return null;
    }

    protected  Object initializeBean(BeanDefinition beanDefinition, String name){
        invokeAwareMethods(name, beanDefinition.getBean());
        for (BeanPostProcess beanPostProcess:beanPostProcesses){
            beanPostProcess.postProcessBeforeInitialization(beanDefinition.getBean(),name);
        }

        if (beanDefinition.getInitMethod() != null || beanDefinition.getInitMethod().length() > 0){
            beanDefinition = custommethodInit(beanDefinition);
        }

        for (BeanPostProcess beanPostProcess:beanPostProcesses){
            beanPostProcess.postProcessAfterInitialization(beanDefinition.getBean(),name);
        }

        if (beanDefinition.getBean() instanceof InitializingBean){
            try {
                ((InitializingBean)(beanDefinition.getBean())).afterPropertiesSet();
            } catch (Exception e) {
                throw new RuntimeException("InitializingBean afterPropertiesSet error!!!");
            }
        }

        return beanDefinition.getBean();
    }

    private void invokeAwareMethods(String name, Object bean) {
        if (bean instanceof BeanNameAware){
            ((BeanNameAware)bean).setBeanName(name);
        }
        if (bean instanceof BeanFactoryAware){
            ((BeanFactoryAware)bean).setBeanFactory(this);
        }
    }

    protected  BeanDefinition custommethodInit(BeanDefinition beanDefinition){
        try {
            Method method = beanDefinition.getBeanClass().getMethod(beanDefinition.getBeanName());
            method.invoke(beanDefinition.getBean());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("custom init method excute error!!!");
        }
        return beanDefinition;
    }

    protected  Object doCreatebean(BeanDefinition beanDefinition) throws Exception {
        Object bean = createBeanInstance(beanDefinition);
        bean = applyPropertyValues(bean, beanDefinition);
        return bean;
    }

    protected abstract Object applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception;

    protected  Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
       if ( beanDefinition.getBeanClass() != null){
           return beanDefinition.getBeanClass().newInstance();
       }else {
           Class clasz = Class.forName(beanDefinition.getClassName());
           beanDefinition.setBeanClass(clasz);
           return clasz.newInstance();
       }
    }

    public void preInstantiateSingletons(){
        for (Map.Entry<String, BeanDefinition> entry:this.beanDefinitionMap.entrySet()){
            try {
                getBean(entry.getKey());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void registerBeanName(String name,BeanDefinition beanDefinition){
        this.beanDefinitionMap.put(name,beanDefinition);
        this.beanDefinitionNames.add(name);
    }

    @Override
    public String[] getbeanNamesfortype(Class classType) {
        List<String> beanNames = new ArrayList<String>();
        for (String beanDefinitionName : beanDefinitionNames) {
            if (classType.isAssignableFrom(beanDefinitionMap.get(beanDefinitionName).getBeanClass())) {
                beanNames.add(beanDefinitionMap.get(beanDefinitionName).getBeanName());
            }
        }
        return beanNames.toArray(new String[beanNames.size()]);
    }

    @Override
    public List getBeansForType(Class type) throws Exception {
        List beans = new ArrayList<Object>();
        for (String beanDefinitionName : beanDefinitionNames) {
            if (type.isAssignableFrom(beanDefinitionMap.get(beanDefinitionName).getBeanClass())) {
                beans.add(getBean(beanDefinitionName));
            }
        }
        return beans;
    }

    @Override
    public boolean containsBean(String name) {
        return beanDefinitionMap.containsKey(name);
    }

    public List<BeanPostProcess> getBeanPostProcesses() {
        return beanPostProcesses;
    }

    public void setBeanPostProcesses(List<BeanPostProcess> beanPostProcesses) {
        this.beanPostProcesses = beanPostProcesses;
    }
}